﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using MongoDB.Driver;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.ESG.Entities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Sustainalytics.DataLayer.Tests.Shared
{
    public static class Helper
    {
        public static string ToStringNullSafe(this object obj)
        {
            return obj != null ? obj.ToString() : String.Empty;
        }
    }

    [TestClass]
    public class RepositoryCollectionTests
    {
        private readonly string _collectionName = typeof(Insight).Name;
        private const string DatabaseName = "UnitTests";
        private const string DbCnx = "mongodb://localhost:27017/UnitTests";
        private IMongoDatabase _database;

        [TestInitialize]
        public void Initialize()
        {
            var mongoClient = new MongoClient(DbCnx);
            _database = mongoClient.GetDatabase(DatabaseName);
            _database.GetCollection<Insight>(_collectionName).DeleteMany(_ => true);
        }

        [TestMethod]
        public void ReadAllWhere_Test()
        {
            var col = _database.GetCollection<Insight>(_collectionName);

            var expected = new Dictionary<Guid, Insight>();
            for (var i = 0; i < 10; i++)
            {
                var ni = new Insight { Id = Guid.NewGuid(), Title = i.ToString() };
                expected.Add(ni.Id, ni);
                col.InsertOne(ni);
            }

            //TODO: do not test two methods in same test method
            var rep = new RepositoryCollection<Insight>(DbCnx);
            var found1 = rep.ReadAllWhereFieldEqualsValue("Title", "9", null, null).SingleOrDefault();
            Assert.AreEqual(found1.Title, "9", "ReadAllWhereFieldEqualValue fails");
        }

        [TestMethod]
        public void ReadbyIdsTest()
        {
            var col = _database.GetCollection<Insight>(_collectionName);

            var expected = new Dictionary<Guid, Insight>();
            for (var i = 0; i < 10; i++)
            {
                var ni = new Insight { Id = Guid.NewGuid(), Title = i.ToString() };
                expected.Add(ni.Id, ni);
                col.InsertOne(ni);
            }

            var rep = new RepositoryCollection<Insight>(DbCnx);
            var found = rep.ReadbyIds(expected.Keys);

            Assert.IsTrue(
                AreListsEqualByElementPublicProperty(found.Values.OrderBy(x => x.Id), expected.Values.OrderBy(x => x.Id)),
                "ReadbyIds fails");
        }

        private static bool AreEqualByPublicProperty<T>(T a, T b, string msg, params string[] ignore)
        {
            if ((a != null && b == null) || (a == null && b != null))
            {
                throw new AssertFailedException("Null input: " + msg);
            }

            if (a != null)
            {
                var aProps = a.GetType().GetProperties();
                var bProps = b.GetType().GetProperties();
                int count = aProps.Count();
                for (int i = 0; i < count; i++)
                {
                    var aa = aProps[i].GetValue(a, null).ToStringNullSafe();
                    var bb = bProps[i].GetValue(b, null).ToStringNullSafe();
                    if (aa != bb && ignore.All(x => x != aProps[i].Name))
                    {
                        throw new AssertFailedException("properties dont match: " + aProps[i].Name + ", " + msg);
                    }
                }
            }
            return true;
        }

        public static bool AreListsEqual<T>(IEnumerable<T> a, IEnumerable<T> b) where T : class
        {
            var arrA = a.ToArray();
            var arrB = b.ToArray();

            if (arrA.Length == arrB.Length && arrB.Length > 0)
            {
                for (var i = 0; i < arrB.Length; i++)
                {
                    if (!arrA[i].Equals(arrB[i]))
                    {
                        return false;
                    }
                }
                return true;
            }

            return false;
        }

        private static bool AreListsEqualByElementPublicProperty<T>(IEnumerable<T> a, IEnumerable<T> b) where T : class
        {
            var arrA = a.ToArray();
            var arrB = b.ToArray();

            if (arrA.Length == arrB.Length && arrB.Length > 0)
            {
                for (var i = 0; i < arrB.Length; i++)
                {
                    if (!AreEqualByPublicProperty(arrA[i], arrB[i], "failed in index " + i))
                    {
                        return false;
                    }
                }
                return true;
            }

            return false;
        }

        [TestMethod()]
        public void ReadCountTest()
        {
            var count = 1000;
            var ulist = new List<Tuple<string, object, string, object, bool>>();
            var iList = new List<CorporateData>();

            for (int i = 0; i < count; i++)
            {
                if (i % 2 == 0)
                {
                    var cd = new CorporateData
                    {
                        Id = Guid.NewGuid(),
                        CompanyName = "company I" + i,
                        UniverseIds = (new int[] { 1, 3, 5, 7 }).ToList()
                    };
                    iList.Add(cd);
                }
                else
                {
                    var cd = new CorporateData
                    {
                        Id = Guid.NewGuid(),
                        CompanyName = "company I" + i,
                        UniverseIds = null
                    };
                    iList.Add(cd);
                }
            }

            _database.GetCollection<CorporateData>("CorporateData").DeleteMany(_ => true);
            _database.GetCollection<CorporateData>("CorporateData").InsertMany(iList);

            var rep = (IRepositoryCollection<CorporateData>)new RepositoryCollection<CorporateData>(DbCnx);

            var res = rep.ReadCount(x => x.UniverseIds == null);
            Assert.IsTrue((count / 2) == res);
        }

        [TestMethod()]
        public void ToStringTest()
        {
            var rep = new RepositoryCollection<CorporateData>("mongodb://localhost/UnitTests");

            Assert.IsTrue("UnitTests/CorporateData".Equals(rep.ToString()));
        }
        
        private class Test : IEntity
        {
            public Guid Id { get; set; }
            public string Text { get; set; }
            public int Val { get; set; }
        }

        [TestMethod()]
        public void CreateBatchParallelTest()
        {
            var size = 1000000;
            var sw = Stopwatch.StartNew();

            var input = Enumerable.Range(0, size).Select(x => new Test() { Id = Guid.NewGuid(), Val = x, Text = x.ToString() }).ToArray();
            var rep = new RepositoryCollection<Test>(DbCnx, "ParalelTest");
            rep.DropAndCreate();

            sw.Restart();
            rep.CreateBatchParallel(input, 1000, 20);
            sw.Stop();

            var paralel = sw.ElapsedMilliseconds;

            var rep2 = new RepositoryCollection<Test>(DbCnx, "ParalelTest2");
            rep2.DropAndCreate();
            sw.Restart();
            rep2.CreateBatch(input);
            sw.Stop();
            var sync = sw.ElapsedMilliseconds;

            Assert.AreEqual(size, (int)rep.ReadCount());
            Assert.AreEqual(size, (int)rep2.ReadCount());
            Assert.IsTrue(sync > paralel);
        }
    }
}
